package com.novel.crawler.utils;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Component
public class HttpClientUtil {

    //HttpPost和HttpGet的父类，根据传入的数值来确定实例化成那个来发起请求
    private HttpRequestBase httpBase = null;
    //连接池
    private static PoolingHttpClientConnectionManager pc;

    //静态代码块加载连接池参数，初始化连接池
    static {
        pc = new PoolingHttpClientConnectionManager();
        pc.setMaxTotal(20);
        pc.setDefaultMaxPerRoute(10);
    }

    //从连接池获取链接
    private static CloseableHttpClient getClient() {
        return HttpClients.custom().setConnectionManager(pc).build();
    }

    /**
     * 用来执行发起请求，boolean标识用那种方式发起请求
     *
     * @param url 地址
     * @param Get 是否使用get请求
     * @return html html字符串
     * @throws IOException
     */
    public String execute(String url, boolean Get) throws IOException {
        //从连接池获取连接
        CloseableHttpClient client = getClient();
        //根据输入的参数来判断要通过那种方式发起请求
        if (Get) {
            httpBase = new HttpGet(url);
        } else {
            httpBase = new HttpPost(url);
        }
        //为请求添加请求头
        readProperties(httpBase);
        //为请求添加配置信息
        httpBase.setConfig(getConfig());
        //返回获得的html文件
        return ResponseFinish(client.execute(httpBase));
    }


    /**
     * 为请求添加中断时间等，避免浪费时间，也更好的封装请求
     *
     * @return 请求配置信息
     */
    private RequestConfig getConfig() {
        RequestConfig config = RequestConfig.custom()
                .setConnectTimeout(1000)//设置最大连接时间
                .setConnectionRequestTimeout(500)//设置最长请求时间
                .setSocketTimeout(10 * 1000)//设置传输的最大时间
                .build();
        return config;
    }


    /**
     * 为请求添加请求头
     * 从配置文件中读取配置信息，并为请求添加请求头
     *
     * @param httpBase Http请求
     */
    private void readProperties(HttpRequestBase httpBase) {
        //加载配置文件
        ResourceBundle bundle = ResourceBundle.getBundle("requestHeader");
        //获取键值对中键集合的迭代器
        Enumeration<String> keys = bundle.getKeys();
        //通过迭代器来遍历
        while (keys.hasMoreElements()) {
            //从迭代器中取出键的值
            String key = keys.nextElement();
            //把键值对添加到请求中
            httpBase.addHeader(key, bundle.getString(key));
        }
    }

    /**
     * 通过获取响应头中文本格式来获取编码格式
     * 避免乱码
     *
     * @param contentType 响应头中关于返回文本格式
     * @return charset 编码格式
     */
    private String parseCharset(String contentType) {
        //定义正则处理规则
        String regex = ".*charset=([^;]*).*";
        Pattern pattern = Pattern.compile(regex);
        //加载要处理的字符串
        Matcher matcher = pattern.matcher(contentType);
        if (matcher.find()) {
            //获取成功，返回相应编码格式
            return matcher.group(1);
        }
        //获取失败返回默认编码
        return "utf-8";
    }

    /**
     * 解析响应
     *
     * @param response 响应体
     * @return html html字符串
     * @throws IOException
     */
    private String ResponseFinish(CloseableHttpResponse response) throws IOException {
        //判断请求是否正常
        if (response.getStatusLine().getStatusCode() == 200) {
            //请求正常，获得响应体
            HttpEntity entity = response.getEntity();
            //解析响应体中的编码格式
            String charset = parseCharset(entity.getContentType().toString());
            //根据获得的编码格式读取响应内容并返回
            return EntityUtils.toString(entity, charset);
        }
        return null;
    }
}
